// Define a TypeScript interface
interface Person {
    name: string
    age: number
}

// Create an array of objects with the defined interface
const people: Person[] = [
    { name: 'Alice', age: 30 },
    { name: 'Bob', age: 25 },
    { name: 'Charlie',
        age: 35 },
]

const log = console.log

// Use a for...of loop to iterate over the array
for (const person of people) {
    log(`Hello, my name is ${person.name} and I am ${person.age} years old.`)
}

// Define a generic function
function identity<T>(arg: T): T {
    return arg
}

// Use the generic function with type inference
const result = identity(
    'TypeScript is awesome')
log(result)

// Use optional properties in an interface
interface Car {
    make: string
    model?: string
}

// Create objects using the interface
const car1: Car = { make: 'Toyota' }
const car2: Car = {
    make: 'Ford', model: 'Focus' }

// Use union types
type Fruit = 'apple' | 'banana' | 'orange'
const favoriteFruit: Fruit = 'apple'

// Use a type assertion to tell TypeScript about the type
const inputValue: any = '42'
const numericValue = inputValue as number

// Define a class with access modifiers
class Animal {
    private name: string
    constructor(name: string) {
        this.name = name
    }

    protected makeSound(sound: string) {
        log(`${this.name} says ${sound}`)
    }
}

// Extend a class
class Dog extends Animal {
    constructor(private alias: string) {
        super(alias)
    }

    bark() {
        this.makeSound('Woof!')
    }
}

const dog = new Dog('Buddy')
dog.bark()

function fn(): string {
    return `hello${1}`
}

log(car1, car2, favoriteFruit, numericValue, fn())

// Generator
export function* generator1() {
    let id = 0
    while (id < 100) {
        yield id++
    }
}
export function* generator2() {
    yield* generator1()
}

export {}
